using System;
using System.Collections.Generic;
using System.Text;

using System.IO.IsolatedStorage;
using System.Security.Cryptography;
using System.IO;
using System.Xml.Serialization;
using System.Xml;

namespace CryptoUtils
{
    /// <summary>
    /// Container for symmetric key safe storing 
    /// </summary>
    public class SymmetricKeyContainer
    {
        
        private byte[] _key;
        private byte[] _iv;
        private string _name;
        private DataProtectionScope _scope;
        private bool _invalid;

        /// <summary>
        /// Creates named container and loads key from storage
        /// </summary>
        /// <param name="name">Container name</param>
        /// <exception cref="System.ArgumentNullException">'name' is a null or empty string</exception>
        /// <exception cref="System.Security.Cryptography.CryptographicException">named container isn't existing in the storage</exception>
        public SymmetricKeyContainer(string name) : this(name, DataProtectionScope.CurrentUser) { }

        /// <summary>
        /// Creates named container and loads key from storage
        /// </summary>
        /// <param name="name">Container name</param>
        /// <param name="scoupe">Scoupe of the key storage and protection</param>
        /// <exception cref="System.ArgumentNullException">'name' is a null or empty string</exception>
        /// <exception cref="System.Security.Cryptography.CryptographicException">named container isn't existing in the storage</exception>
        public SymmetricKeyContainer(string name, DataProtectionScope scoupe)
        {
            if (name == null || name.Length == 0)
                throw new ArgumentNullException("name");
            _name = name;
            _scope = scoupe;
            _invalid = false;
            try
            {
                LoadFromStorage();
            }
            catch (Exception ex)
            {
                throw new CryptographicException(String.Format("Unable to open SymmetricKeyContainer '{0}' ", name), ex);
            }
        }

        /// <summary>
        /// Creates new named container and save key in storage 
        /// </summary>
        /// <param name="name">container name</param>
        /// <param name="scoupe">Scoupe of the key storage and protection</param>
        /// <param name="key">key</param>
        /// <param name="iv">IV</param>
        /// <exception cref="System.ArgumentNullException">one of the parameters is a null</exception>
        /// <exception cref="System.Security.Cryptography.CryptographicException">named container isn't existing in the storage</exception>
        public SymmetricKeyContainer(string name, DataProtectionScope scoupe, byte[] key, byte[] iv)
        {
            if (name == null || name.Length == 0) throw new ArgumentNullException("name");
            if (key == null) throw new ArgumentNullException("key");
            if (iv == null) throw new ArgumentNullException("iv");
            _name = name;
            _key = key;
            _iv = iv;
            _scope = scoupe;
            _invalid = false;
            try
            {
                SaveInStorage();
            }
            catch (Exception ex)
            {
                throw new CryptographicException(String.Format("Unable to create SymmetricKeyContainer '{0}' ", name), ex);
            }
        }

        /// <summary>
        /// Gets contaiter name
        /// </summary>
        public string Name
        {
            get
            {
                Invalidate();
                return _name;
            }
        }

        /// <summary>
        /// Gets key
        /// </summary>
        public byte[] Key
        {
            get
            {
                Invalidate();
                return _key;
            }
        }

        /// <summary>
        /// Gets IV
        /// </summary>
        public byte[] IV
        {
            get 
            {
                Invalidate();
                return _iv;
            }
        }

        /// <summary>
        /// Clear key container in memory and in the storage
        /// </summary>
        public void Clear()
        {
            if (_invalid) return;
            _invalid = true;
            _key = null;
            _iv = null;
            IsolatedStorageFile storage = GetIsolatedStorage();
            storage.DeleteFile(_name);
        }

        void Invalidate()
        {
            if (_invalid) throw new CryptographicException("SymmetricKeyContainer is empty");
        }

        private IsolatedStorageFile GetIsolatedStorage()
        {
            if (_scope == DataProtectionScope.LocalMachine)
                return IsolatedStorageFile.GetMachineStoreForDomain();
            else return IsolatedStorageFile.GetUserStoreForDomain();
        }

        byte[] salt = new byte[] { 143, 25, 4, 30, 11, 83 };
        void SaveInStorage() 
        {
            
            byte[] cryptedKey = ProtectedData.Protect(_key, salt, _scope);
            byte[] cryptedIV = ProtectedData.Protect(_iv, salt, _scope);

            IsolatedStorageFile iStorage = GetIsolatedStorage();
            using (BinaryWriter writer = new BinaryWriter(new IsolatedStorageFileStream(_name, FileMode.Create, iStorage)))
            {
                writer.Write(cryptedKey.Length);
                writer.Write(cryptedIV.Length);
                writer.Write(cryptedKey);
                writer.Write(cryptedIV);
            }
        }

        
        void LoadFromStorage()
        {
            byte[] encryptedKey;
            byte[] encryptedIV;
            IsolatedStorageFile iStorage = GetIsolatedStorage();
            using (BinaryReader reader = new BinaryReader(new IsolatedStorageFileStream(_name, FileMode.Open, iStorage)))
            {
                int keyLength = reader.ReadInt32();
                int ivLength = reader.ReadInt32();
                
                encryptedKey = new byte[keyLength];
                encryptedIV = new byte[ivLength];

                reader.Read(encryptedKey, 0, encryptedKey.Length);
                reader.Read(encryptedIV, 0, encryptedIV.Length);

                _key = ProtectedData.Unprotect(encryptedKey, salt, DataProtectionScope.LocalMachine);
                _iv = ProtectedData.Unprotect(encryptedIV, salt, DataProtectionScope.LocalMachine);
            }
        }

        #region Import/Export
        /// <summary>
        /// Class for Xml serializing SymmetricKeyContainer's state 
        /// </summary>
        [XmlRoot(ElementName = "SymmetricKeyContainer", Namespace = "urn:CryptoUtils.org")]
        public class SerializableState
        {
            /// <summary>
            /// default constructor
            /// </summary>
            public SerializableState() { }
            internal SerializableState(string name, byte[] key, byte[] iv)
            {
                Name = name;
                Key = key;
                IV = iv;
            }
            /// <summary>
            /// Key
            /// </summary>
            [XmlIgnore]
            public byte[] Key;
            /// <summary>
            /// IV
            /// </summary>
            [XmlIgnore]
            public byte[] IV;
            /// <summary>
            /// Name
            /// </summary>
            [XmlAttribute]
            public string Name;
            /// <summary>
            /// KeyString
            /// </summary>
            [XmlElement("Key")]
            public string KeyString
            {
                get
                {
                    return Convert.ToBase64String(Key);
                }
                set
                {
                    Key = Convert.FromBase64String(value);
                }
            }
            /// <summary>
            /// IVString
            /// </summary>
            [XmlElement("IV")]
            public string IVString
            {
                get
                {
                    return Convert.ToBase64String(IV);
                }
                set
                {
                    IV = Convert.FromBase64String(value);
                }
            }

            internal static SymmetricKeyContainer Load(string fileName)
            {
                SerializableState state;
                using (FileStream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read))
                {
                    XmlSerializer sr = new XmlSerializer(typeof(SymmetricKeyContainer.SerializableState));
                    state = (SerializableState)sr.Deserialize(stream);
                }
                return new SymmetricKeyContainer(state.Name, DataProtectionScope.CurrentUser, state.Key, state.IV);
            }

            internal void Save(string fileName)
            {
                XmlSerializer sr = new XmlSerializer(this.GetType());
                using (XmlTextWriter writer = new XmlTextWriter(fileName, Encoding.UTF8))
                {
                    writer.Formatting = Formatting.Indented;
                    sr.Serialize(writer, this);
                }
            }
        }

        /// <summary>
        /// Exports key container to file
        /// </summary>
        /// <param name="fileName"></param>
        public void ExportToFile(string fileName)
        {
            new SymmetricKeyContainer.SerializableState(this.Name, this.Key, this.IV).Save(fileName);
        }

        /// <summary>
        /// Imports key container from file
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public static SymmetricKeyContainer ImportFromFile(string fileName)
        {
            return SymmetricKeyContainer.SerializableState.Load(fileName);
        }
        #endregion

    }
}
